package personnel.department.pages.widgets;

import static com.google.common.base.Preconditions.checkNotNull;
import static personnel.department.utils.ReflectionUtils.isAssignableOfUniqueObject;

import java.lang.reflect.Field;
import java.util.List;

import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

import personnel.department.dao.entity.Career;
import personnel.department.dao.entity.Education;
import personnel.department.dao.entity.UniqueObject;
import personnel.department.pages.constants.Colors;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;

public abstract class AbstractWidget<T> {
	
	protected T currentValue;
	protected T oldValue;
	private Field field;

	public AbstractWidget(Field field) {
		this.field = field;
		if (field != null) { 
		field.setAccessible(true);
		}
	}
	
	public abstract T getValue();

	protected abstract Control getWidget();
	
	public abstract void setValue(T value);
	
	public boolean isModify() {
		return !Objects.equal(oldValue, currentValue);
	}
	
	protected void create() {
		currentValue = oldValue = getValue();
		Display display = getWidget().getDisplay();
		getWidget().addFocusListener(new FocusListener() {
			
			@Override
			public void focusLost(FocusEvent e) {
				getWidget().setBackground(null);
			}
			
			@Override
			public void focusGained(FocusEvent e) {
				getWidget().setBackground(Colors.getFocusWidgetColor(display));
				
			}
		});
	}
	
	protected void modify() {
		currentValue = getValue();
	}
	
	public void setValueByObject(UniqueObject object) throws Exception {
		setValueByObject(object, null);
	}
	
	
	public void setValueByObject(UniqueObject object, Class<?>... ignoredClasses) throws Exception {
		checkNotNull(field, "field can't be null");
		checkNotNull(object, "UniqueObject can't be null");
		List<Class<?>> ignored = Lists.newArrayList();
		if (ignoredClasses != null) {
			ignored.addAll(ignored);
		}
		setValueByObject(object, false, ignored);
	}
	
	
	
	public boolean setValueByObject(Object object, boolean isFind, List<Class<?>> ignored) throws Exception {
		if (isFind) {
			return true;
		}
		
		if (object.getClass().equals(getFieldType())) {
			field.setAccessible(true);
			setValue((T) field.get(object));
			return true;
		}
		
		for (Field subField : object.getClass().getDeclaredFields()) {
				if (isCurrectClass(subField.getType(), ignored)) {
					subField.setAccessible(true);
					isFind = setValueByObject((Object) subField.get(object), isFind, ignored);
				}
		}
		
		return isFind;
		
	}
	
	private boolean isCurrectClass(Class<?> currentClass, List<Class<?>> ignored) {
		return (isAssignableOfUniqueObject(currentClass) 
				|| currentClass.equals(Education.class) 
				|| currentClass.equals(Career.class))
				&& !ignored.contains(currentClass);
	}
	
	
	@SuppressWarnings("unchecked")
	public void setValueForObject(UniqueObject object) throws Exception {
		checkNotNull(field, "field can't be null");
		checkNotNull(object, "UniqueObject can't be null");
		if (!getFieldType().equals(object.getClass())) {
			for (Field subField : object.getClass().getDeclaredFields()) {
				if (subField.getType().equals(getFieldType())) {
					subField.setAccessible(true);
					object = (UniqueObject) subField.get(object);
					break;
				}
			}
		}
		field.set(object, getValue());
	}
	
	public String getFieldName() {
		return field.getName();
	}
	
	public void setEnabled(boolean enabled) {
		getWidget().setEnabled(enabled);
	}
	
	public Class<?> getFieldType() {
		return field.getDeclaringClass();
	}
	
	public void reset() {
		setValue(currentValue = oldValue);
	}
	
}
